Data Wrangling

Retrieve and Prepare Data

The data were retrieved via R package httr with some initial conversion to data.table objects. Core objects were cached to disk (cachem) for easy retrieval after the initial pull.

Data Sets

MDRP Data

if (!"api_data" %in% .cache$keys()){ 
  download_temp <- tempfile()
  
  as.character(urls$data) |> 
    stri_extract_all_regex("http.+csv", simplify = TRUE) |> 
    as.vector() |>
    download.file(destfile = download_temp) 
    
  .tmp_obj <- read.csv(download_temp) |> as.data.table(na.rm = FALSE) 
}

if (!"api_data" %in% ls()){ 
  makeActiveBinding("api_data", function(){ .cache$get("api_data") }, env = globalenv())
}

Formatting updates include the following:

  • Convert field ‘NDC’ and fields ending in ‘Code’ to characters (numeral-encoded nominal values)
  • Converting date fields to date format
  • Replace ‘.’ in field names with ’ ’

Dictionary

if (!"api_dictionary" %in% .cache$keys()){ 
  .cache$set("api_dictionary", invisible( 
    as.character(urls$data) |> 
    stri_extract_all_regex("http.+pdf", simplify = TRUE) |> 
    as.vector() |>
    GET() |>
    content() |> 
    pdf_text()))
}  

.summary_labels <- invisible({
  .pattern <- c("Pkg"
                , "Intro"
                , "COD Status"
                , "FDA Application Number"
                , "FDA Therapeutic Equivalence Code"
                );
  .replacement <- c("Package"
                , "Intro.+Date"
                , "Covered Outpatient Drug [(]COD[)] Status"
                , "FDA Application Number/OTC Monograph Number"
                , "TEC"
                );
  
  names(api_data) |>
    rlang::set_names() |>
    imap_chr(\(x, y){
      .out <- .cache$get("api_dictionary") |> 
        stri_extract_all_regex(
          sprintf(
            fmt = "(%s)[:]\n.+"
            , stri_replace_all_fixed(str = x, pattern = .pattern , replacement = .replacement, vectorize_all = FALSE) 
            )
        , simplify = TRUE
        ) |>
        stats::na.omit() |>
        as.vector() |> paste(collapse = "\n")
      
      if (rlang::is_empty(.out)){ 
        y 
      } else{ 
        .out <- paste(.out, collapse = "\n")
        ifelse(stringi::stri_length(.out) > 50, paste0(stri_sub(.out, length = 50), " ..."), .out)
      }
    })
})

.tmp_obj <- api_data;

iwalk(.summary_labels, \(x, y){ 
  .tmp_obj <<- modify_at(.tmp_obj, y, \(i){ attr(i, "label") <- x; i }) 
})

.cache$set("api_data", .tmp_obj)

OpenFDA Data

if (!"open_fda_ndc" %in% .cache$keys()){
  json.file <- paste0(params$data_dir, "/drug-ndc-0001-of-0001.json");
  download.file <- tempfile();
  
  if (!file.exists(json.file)){ 
    tags$p(sprintf("Retrieve data from '%s'", urls$openFDA)) |> print()
    
    GET(urls$data$children |> stri_extract_first_regex("https.+json.zip"),
      write_disk(path = download.file, overwrite = TRUE));
    
    unzip(zipfile = download.file, exdir = "data")
  }
      
  .cache$set("open_fda_ndc", { read_json(path = json.file) %$% {
    map(results, as.data.table) |> rbindlist(fill = TRUE) |>  
      setattr("metadata", meta)}
    })
}

if (!"openFDA_ndc" %in% ls()){ 
    makeActiveBinding("openFDA_ndc", function(){ .cache$get("open_fda_ndc")}, env = environment())
  }

NDC Format Inspection

NDC sequences come in a various formats, usually a 4-4-x, 5-4-x, or 5-3-x sequence (each integer indicating string length). Sometimes other formats arise, so normalizing all NDC sequences is a good idea, especially when there is a desire (or need) to join different data containing intersecting NDCs.

The following shows proportional representation of NDC formats in the OpenFDA and MDRP data, respectively:

The MDRP has many more NDC sequences due to truncation of leading zeroes. Fortunately, an NDC sequence is a collection of code segments (present in the data) concatenated with a hyphen. Knowing this, A function (check_ndc_format() — see setup.R was created in order to derive conformed NDC segment sequences (using labeler and product codes) based on the OpenFDA sequences, allowing the MDRP and OpenFDA data to be joined later in the process.

Master Drug Data

The OpenFDA and MDRP data were joined using the conformed NDC sequence in the previous subsection to create master_drug_data (Note: due to the size of the data, the join operation takes some time which is why the result is disk-cached for later retrieval. This caching approach is used for data retrieved or created during the data wrangling phase):

if (params$refresh || !"master_drug_data" %in% .cache$keys()){
  .cache$set("master_drug_data", (\(x, i){
    x[i
      , on = "alt_ndc==product_ndc"
      , allow.cartesian = TRUE
      , `:=`(pharm_class = pharm_class
             , dea_schedule = dea_schedule
             , product_type = product_type
             , route = route
             , marketing_category = marketing_category
             )
      , by = .EACHI
      ][
      , `:=`(
        pharm_class = map_chr(pharm_class, \(x) unlist(x) %||% "~")
        , route = map_chr(route, \(x) unlist(x) %||% "~")
        )
      ]
    })(
    api_data %>% 
      setnames(stri_replace_all_fixed(names(.) |> tolower(), " ", "_")) %>% 
      .[, alt_ndc := map2_chr(labeler_code, product_code, check_ndc_format)]
    , openFDA_ndc
    ))
}
if (!"master_drug_data" %in% ls()){ 
  makeActiveBinding("master_drug_data", function(){ .cache$get("master_drug_data") }, env = globalenv());
}

Event Metrics

master_drug_data is a great data set for constructing simple, time-based metrics. Given the natural order of the types of events, it is easy to setup event sequence metrics using package lubridate. The metrics to be created are described below:

Metric Name Description
days_to_market Days between approval and market release
on_market_age Days active on market
days_market_absent Days most-recently absent from market

Drug Events
Creation

My next task was to add the date-differential metrics mentioned in the previous subsection. As an intermediate object, I created ndc_events by looking at what appears to the be natural chronology of dates: fda_approval_date -> market_date -> termination_date -> reactivation_date.

Some of the values in columns termination_date and reactivation_date are NA indicating the event did not happen. This would obviously need to be addressed in deriving the metrics logic, and after several rounds of trial-and-error, I worked out such logic, discovering the following in the process:

  • The metrics are hierarchically-contingent based on whether or not NA values exist and if so, which of termination_date, reactivation_date, or both
  • Some values in termination_date and reactivation_date are future-dated relative to “today”: these were converted to NA before deriving the metrics as they haven’t happened yet (NA \(\equiv\) Didn’t happen (yet))
  • A small subset of observations having the FDA approval date after the listed market date

The resulting object was captured in ndc_events_clean:

ndc_events_clean <- { 
  define(
    ndc_events
    , modify_at(.SD, c("termination_date", "reactivation_date"), \(x) ifelse(today() < x, NA, x))
    , cbind(
        .SD
        , define({
            .SD[, fda_approval_date:reactivation_date][, map(.SD, as.numeric)] |> 
              # dplyr::slice_sample(prop = 0.4) |>
              apply(1, \(x){
                c(x, diff(x) |> modify_if(is.na, \(i) 0) |> sign() %>% .[-1]) |> 
                  as.list() |>
                  modify_at(c(5, 6), \(i) i == 1) %>% 
                  rlang::set_names(names(.)[c(1:4)], paste0(names(.)[c(5, 6)], ".bool"))
                }, simplify = FALSE) |>
              rbindlist()
            }
          , days_to_market = market_date - fda_approval_date
          , on_market_age = 
              apply(.SD[, .(termination_date.bool, reactivation_date.bool, termination_date)]
                    , 1, function(i){ ifelse(i[[1]], ifelse(i[[2]], today(), i[[3]]), today()) }) -
              apply(.SD[, .(termination_date.bool, reactivation_date.bool, market_date, reactivation_date)]
                    , 1, function(i){ ifelse(i[[1]], ifelse(i[[2]], i[[4]], i[[3]]), i[[3]]) })
          , days_market_absent = 
              apply(.SD[, .(reactivation_date.bool, reactivation_date)]
                    , 1, function(i){ ifelse(i[[1]], i[[2]], today()) }) -
                apply(.SD[, .(termination_date.bool, termination_date)]
                    , 1, function(i){ ifelse(i[[1]], i[[2]], today()) })
          , ~days_to_market + on_market_age + days_market_absent
          )
      )
    , modify_at(.SD, c("termination_date", "reactivation_date"), \(x) as.Date(x, origin = "1970-01-01"))
  )}

#
(\(x, i, by){
  i <- define(x[i, on = by, allow.cartesian = TRUE]) ;
  imap(.ndc_events_meta, \(x, y){
    rlang::inject(descr(x = modify_at(i, y, \(j) as.numeric(j, units = "days")), var = !!rlang::sym(y), transpose = !TRUE)) |> 
      view(method = "render", table.classes = 'multi_stat', custom.css = "markdown.css") |>
      tags$td()
  })
})(master_drug_data, ndc_events_clean, c("alt_ndc", "fda_application_number", "market_date", "termination_date", "reactivation_date", "fda_approval_date")) |>
tags$tr() |>
tags$table()

Descriptive Statistics

days_to_market

N: 1417407
days_to_
market
Mean 786.30
Std.Dev 1850.40
Min -9859.00
Q1 0.00
Median 59.00
Q3 378.00
Max 11870.00
MAD 87.47
IQR 378.00
CV 2.35
Skewness 3.00
SE.Skewness 0.00
Kurtosis 9.40
N.Valid 1417407
Pct.Valid 100.00

Generated by summarytools 1.0.1 (R version 4.1.3)
2023-06-04

Descriptive Statistics

on_market_age

N: 1417407
on_market_
age
Mean 5305.24
Std.Dev 3320.22
Min 1.00
Q1 2757.00
Median 4405.00
Q3 7319.00
Max 11935.00
MAD 3033.40
IQR 4562.00
CV 0.63
Skewness 0.71
SE.Skewness 0.00
Kurtosis -0.58
N.Valid 1417407
Pct.Valid 100.00

Generated by summarytools 1.0.1 (R version 4.1.3)
2023-06-04

Descriptive Statistics

days_market_absent

N: 1417407
days_market_
absent
Mean 160.57
Std.Dev 638.36
Min -3007.00
Q1 0.00
Median 0.00
Q3 0.00
Max 11112.00
MAD 0.00
IQR 0.00
CV 3.98
Skewness 4.87
SE.Skewness 0.00
Kurtosis 30.26
N.Valid 1417407
Pct.Valid 100.00

Generated by summarytools 1.0.1 (R version 4.1.3)
2023-06-04

Some of the ‘Max’/‘Min’ values are negative; however, the number of records is relatively small and, more importantly, explainable:

  • days_to_market: Approval occurred after the market date
  • days_market_absent: Records where the termination date was non-NA but after the market date

Drug Events
Visualization

Combining the master drug data and event data (master_drug_data + ndc_events_clean), after some trial-and-error, I settled on the following showing the root-mean-square of metric values grouped by route of administration:

LS0tDQp0aXRsZTogIkRhdGEgUmV0cmlldmFsIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIGNzczogbWFya2Rvd24uY3NzDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgY3NzOiBtYXJrZG93bi5jc3MNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCnBhcmFtczoNCiAgZGF0YV9kaXI6IGRhdGENCiAgY3Jhbl9saWJzOiAhciBjKCdwdXJycicsICdqc29ubGl0ZScsICdodHRyJywgJ3N1bW1hcnl0b29scycsICdtdW5zZWxsJywgJ2NhY2hlbScsICdTbWFydEVEQScsICdodG1sdG9vbHMnLCAnc2xpZGVyJywgJ3N0cmluZ2knLCAnbWFncml0dHInLCAncGxvdGx5JywgJ0RUJywgJ2RhdGEudGFibGUnLCAncGRmdG9vbHMnLCAnbHVicmlkYXRlJywgJ2Z1dHVyZScsICdmdXJycicsICdmdXR1cmUuY2FsbHInKQ0KICBnaXRfbGliczogIXIgcGFzdGUwKCdib29rLm9mLicsIGMoJ3V0aWxpdGllcycsICdmZWF0dXJlcycsICd3b3JrZmxvdycpKSB8PiBjKCdhcmNoaXRlY3QnLCAnc21hcnQuZGF0YScsICdldmVudC52ZWN0b3JzJykNCiAgcmVmcmVzaDogIXIgRkFMU0UNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgZWNobz1GQUxTRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldCgNCiAgb3B0c19jaHVuayA9IGxpc3QoY2FjaGU9VFJVRSwgY2FjaGUubGF6eT1UUlVFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFKQ0KICApDQoNCnNvdXJjZSgic2V0dXAuUiIsIGxvY2FsPVRSVUUpDQpgYGANCg0KYGBgez1qc30NCjxzY3JpcHQgdHlwZT0idGV4dC9qYXZhc2NyaXB0IiBhc3luYw0KICBzcmM9Imh0dHBzOi8vY2RuanMuY2xvdWRmbGFyZS5jb20vYWpheC9saWJzL21hdGhqYXgvMi43LjcvTWF0aEpheC5qcz9jb25maWc9VGVYLU1NTC1BTV9DSFRNTCI+DQo8L3NjcmlwdD4NCmBgYA0KIyAgey50YWJzZXQgLnRhYnNldC1mYWRlIC50YWJzZXQtcGlsbHN9DQoNCiMjIERhdGEgV3JhbmdsaW5nIA0KDQojIyMgUmV0cmlldmUgYW5kIFByZXBhcmUgRGF0YSB7LnRhYnNldH0gDQoNClRoZSBkYXRhIHdlcmUgcmV0cmlldmVkIHZpYSBSIHBhY2thZ2UgW2h0dHJdKGh0dHBzOi8vaHR0ci5yLWxpYi5vcmcpIHdpdGggc29tZSBpbml0aWFsIGNvbnZlcnNpb24gdG8gW2RhdGEudGFibGVdKGh0dHBzOi8vcmRhdGF0YWJsZS5naXRsYWIuaW8vZGF0YS50YWJsZS8pIG9iamVjdHMuICBDb3JlIG9iamVjdHMgd2VyZSBjYWNoZWQgdG8gZGlzayAoW2NhY2hlbV0oaHR0cHM6Ly9jYWNoZW0uci1saWIub3JnKSkgZm9yIGVhc3kgcmV0cmlldmFsIGFmdGVyIHRoZSBpbml0aWFsIHB1bGwuDQoNCiMjIyMgRGF0YSBTZXRzIA0KDQoqKk1EUlAgRGF0YSoqDQoNCmBgYHtyIFJFVFJJRVZFX0RBVEFfTURSUCwgY29sbGFwc2U9RkFMU0V9IA0KaWYgKCEiYXBpX2RhdGEiICVpbiUgLmNhY2hlJGtleXMoKSl7IA0KICBkb3dubG9hZF90ZW1wIDwtIHRlbXBmaWxlKCkNCiAgDQogIGFzLmNoYXJhY3Rlcih1cmxzJGRhdGEpIHw+IA0KICAgIHN0cmlfZXh0cmFjdF9hbGxfcmVnZXgoImh0dHAuK2NzdiIsIHNpbXBsaWZ5ID0gVFJVRSkgfD4gDQogICAgYXMudmVjdG9yKCkgfD4NCiAgICBkb3dubG9hZC5maWxlKGRlc3RmaWxlID0gZG93bmxvYWRfdGVtcCkgDQogICAgDQogIC50bXBfb2JqIDwtIHJlYWQuY3N2KGRvd25sb2FkX3RlbXApIHw+IGFzLmRhdGEudGFibGUobmEucm0gPSBGQUxTRSkgDQp9DQoNCmlmICghImFwaV9kYXRhIiAlaW4lIGxzKCkpeyANCiAgbWFrZUFjdGl2ZUJpbmRpbmcoImFwaV9kYXRhIiwgZnVuY3Rpb24oKXsgLmNhY2hlJGdldCgiYXBpX2RhdGEiKSB9LCBlbnYgPSBnbG9iYWxlbnYoKSkNCn0NCg0KYGBgDQoNCkZvcm1hdHRpbmcgdXBkYXRlcyBpbmNsdWRlIHRoZSBmb2xsb3dpbmc6DQoNCi0gICBDb252ZXJ0IGZpZWxkICdOREMnIGFuZCBmaWVsZHMgZW5kaW5nIGluICdDb2RlJyB0byBjaGFyYWN0ZXJzIChudW1lcmFsLWVuY29kZWQgbm9taW5hbCB2YWx1ZXMpDQotICAgQ29udmVydGluZyBkYXRlIGZpZWxkcyB0byBkYXRlIGZvcm1hdA0KLSAgIFJlcGxhY2UgJy4nIGluIGZpZWxkIG5hbWVzIHdpdGggJyAnDQoNCmBgYHtyIGVjaG89RkFMU0V9DQppZiAoISJhcGlfZGF0YSIgJWluJSAuY2FjaGUka2V5cygpKXsNCiAgc3VwcHJlc3NXYXJuaW5ncygudG1wX29iaiAlPiUNCiAgICBtb2RpZnlfYXQobHMoLiwgcGF0dGVybiA9ICIoTkRDfENvZGUpJCIpLCBhcy5jaGFyYWN0ZXIpICU+JQ0KICAgIG1vZGlmeV9hdChscyguLCBwYXR0ZXJuID0gIkRhdGUiKSwgbHVicmlkYXRlOjptZHkpICU+JSANCiAgICBzZXRuYW1lcyhzdHJpX3JlcGxhY2VfYWxsX2ZpeGVkKG5hbWVzKC4pLCAiLiIsICIgIikpIHw+DQogICAgKFwoeCkgLmNhY2hlJHNldChrZXkgPSAiYXBpX2RhdGEiLCB2YWx1ZSA9IHgpKSgpKQ0KfQ0KYGBgDQoNCioqRGljdGlvbmFyeSoqDQoNCmBgYHtyIFJFVFJJRVZFX0RBVEFfRElDVElPTkFSWX0NCmlmICghImFwaV9kaWN0aW9uYXJ5IiAlaW4lIC5jYWNoZSRrZXlzKCkpeyANCiAgLmNhY2hlJHNldCgiYXBpX2RpY3Rpb25hcnkiLCBpbnZpc2libGUoIA0KICAgIGFzLmNoYXJhY3Rlcih1cmxzJGRhdGEpIHw+IA0KICAgIHN0cmlfZXh0cmFjdF9hbGxfcmVnZXgoImh0dHAuK3BkZiIsIHNpbXBsaWZ5ID0gVFJVRSkgfD4gDQogICAgYXMudmVjdG9yKCkgfD4NCiAgICBHRVQoKSB8Pg0KICAgIGNvbnRlbnQoKSB8PiANCiAgICBwZGZfdGV4dCgpKSkNCn0gIA0KDQouc3VtbWFyeV9sYWJlbHMgPC0gaW52aXNpYmxlKHsNCiAgLnBhdHRlcm4gPC0gYygiUGtnIg0KICAgICAgICAgICAgICAgICwgIkludHJvIg0KICAgICAgICAgICAgICAgICwgIkNPRCBTdGF0dXMiDQogICAgICAgICAgICAgICAgLCAiRkRBIEFwcGxpY2F0aW9uIE51bWJlciINCiAgICAgICAgICAgICAgICAsICJGREEgVGhlcmFwZXV0aWMgRXF1aXZhbGVuY2UgQ29kZSINCiAgICAgICAgICAgICAgICApOw0KICAucmVwbGFjZW1lbnQgPC0gYygiUGFja2FnZSINCiAgICAgICAgICAgICAgICAsICJJbnRyby4rRGF0ZSINCiAgICAgICAgICAgICAgICAsICJDb3ZlcmVkIE91dHBhdGllbnQgRHJ1ZyBbKF1DT0RbKV0gU3RhdHVzIg0KICAgICAgICAgICAgICAgICwgIkZEQSBBcHBsaWNhdGlvbiBOdW1iZXIvT1RDIE1vbm9ncmFwaCBOdW1iZXIiDQogICAgICAgICAgICAgICAgLCAiVEVDIg0KICAgICAgICAgICAgICAgICk7DQogIA0KICBuYW1lcyhhcGlfZGF0YSkgfD4NCiAgICBybGFuZzo6c2V0X25hbWVzKCkgfD4NCiAgICBpbWFwX2NocihcKHgsIHkpew0KICAgICAgLm91dCA8LSAuY2FjaGUkZ2V0KCJhcGlfZGljdGlvbmFyeSIpIHw+IA0KICAgICAgICBzdHJpX2V4dHJhY3RfYWxsX3JlZ2V4KA0KICAgICAgICAgIHNwcmludGYoDQogICAgICAgICAgICBmbXQgPSAiKCVzKVs6XVxuLisiDQogICAgICAgICAgICAsIHN0cmlfcmVwbGFjZV9hbGxfZml4ZWQoc3RyID0geCwgcGF0dGVybiA9IC5wYXR0ZXJuICwgcmVwbGFjZW1lbnQgPSAucmVwbGFjZW1lbnQsIHZlY3Rvcml6ZV9hbGwgPSBGQUxTRSkgDQogICAgICAgICAgICApDQogICAgICAgICwgc2ltcGxpZnkgPSBUUlVFDQogICAgICAgICkgfD4NCiAgICAgICAgc3RhdHM6Om5hLm9taXQoKSB8Pg0KICAgICAgICBhcy52ZWN0b3IoKSB8PiBwYXN0ZShjb2xsYXBzZSA9ICJcbiIpDQogICAgICANCiAgICAgIGlmIChybGFuZzo6aXNfZW1wdHkoLm91dCkpeyANCiAgICAgICAgeSANCiAgICAgIH0gZWxzZXsgDQogICAgICAgIC5vdXQgPC0gcGFzdGUoLm91dCwgY29sbGFwc2UgPSAiXG4iKQ0KICAgICAgICBpZmVsc2Uoc3RyaW5naTo6c3RyaV9sZW5ndGgoLm91dCkgPiA1MCwgcGFzdGUwKHN0cmlfc3ViKC5vdXQsIGxlbmd0aCA9IDUwKSwgIiAuLi4iKSwgLm91dCkNCiAgICAgIH0NCiAgICB9KQ0KfSkNCg0KLnRtcF9vYmogPC0gYXBpX2RhdGE7DQoNCml3YWxrKC5zdW1tYXJ5X2xhYmVscywgXCh4LCB5KXsgDQogIC50bXBfb2JqIDw8LSBtb2RpZnlfYXQoLnRtcF9vYmosIHksIFwoaSl7IGF0dHIoaSwgImxhYmVsIikgPC0geDsgaSB9KSANCn0pDQoNCi5jYWNoZSRzZXQoImFwaV9kYXRhIiwgLnRtcF9vYmopDQpgYGANCg0KKipPcGVuRkRBIERhdGEqKg0KDQpgYGB7ciBSRVRSSUVWRV9EQVRBX09QRU5GREEsIHdhcm5pbmc9RkFMU0V9DQppZiAoISJvcGVuX2ZkYV9uZGMiICVpbiUgLmNhY2hlJGtleXMoKSl7DQogIGpzb24uZmlsZSA8LSBwYXN0ZTAocGFyYW1zJGRhdGFfZGlyLCAiL2RydWctbmRjLTAwMDEtb2YtMDAwMS5qc29uIik7DQogIGRvd25sb2FkLmZpbGUgPC0gdGVtcGZpbGUoKTsNCiAgDQogIGlmICghZmlsZS5leGlzdHMoanNvbi5maWxlKSl7IA0KICAgIHRhZ3MkcChzcHJpbnRmKCJSZXRyaWV2ZSBkYXRhIGZyb20gJyVzJyIsIHVybHMkb3BlbkZEQSkpIHw+IHByaW50KCkNCiAgICANCiAgICBHRVQodXJscyRkYXRhJGNoaWxkcmVuIHw+IHN0cmlfZXh0cmFjdF9maXJzdF9yZWdleCgiaHR0cHMuK2pzb24uemlwIiksDQogICAgICB3cml0ZV9kaXNrKHBhdGggPSBkb3dubG9hZC5maWxlLCBvdmVyd3JpdGUgPSBUUlVFKSk7DQogICAgDQogICAgdW56aXAoemlwZmlsZSA9IGRvd25sb2FkLmZpbGUsIGV4ZGlyID0gImRhdGEiKQ0KICB9DQogICAgICANCiAgLmNhY2hlJHNldCgib3Blbl9mZGFfbmRjIiwgeyByZWFkX2pzb24ocGF0aCA9IGpzb24uZmlsZSkgJSQlIHsNCiAgICBtYXAocmVzdWx0cywgYXMuZGF0YS50YWJsZSkgfD4gcmJpbmRsaXN0KGZpbGwgPSBUUlVFKSB8PiAgDQogICAgICBzZXRhdHRyKCJtZXRhZGF0YSIsIG1ldGEpfQ0KICAgIH0pDQp9DQoNCmlmICghIm9wZW5GREFfbmRjIiAlaW4lIGxzKCkpeyANCiAgICBtYWtlQWN0aXZlQmluZGluZygib3BlbkZEQV9uZGMiLCBmdW5jdGlvbigpeyAuY2FjaGUkZ2V0KCJvcGVuX2ZkYV9uZGMiKX0sIGVudiA9IGVudmlyb25tZW50KCkpDQogIH0NCg0KYGBgDQoNCiMjIyMgTkRDIEZvcm1hdCBJbnNwZWN0aW9uIHsudGFic2V0fQ0KDQpOREMgc2VxdWVuY2VzIGNvbWUgaW4gYSB2YXJpb3VzIGZvcm1hdHMsIHVzdWFsbHkgYSBgNC00LXhgLCBgNS00LXhgLCBvciBgNS0zLXhgIHNlcXVlbmNlIChlYWNoIGludGVnZXIgaW5kaWNhdGluZyBzdHJpbmcgbGVuZ3RoKS4gU29tZXRpbWVzIG90aGVyIGZvcm1hdHMgYXJpc2UsIHNvIG5vcm1hbGl6aW5nIGFsbCBOREMgc2VxdWVuY2VzIGlzIGEgZ29vZCBpZGVhLCBlc3BlY2lhbGx5IHdoZW4gdGhlcmUgaXMgYSBkZXNpcmUgKG9yIG5lZWQpIHRvIGpvaW4gZGlmZmVyZW50IGRhdGEgY29udGFpbmluZyBpbnRlcnNlY3RpbmcgTkRDcy4NCg0KVGhlIGZvbGxvd2luZyBzaG93cyBwcm9wb3J0aW9uYWwgcmVwcmVzZW50YXRpb24gb2YgTkRDIGZvcm1hdHMgaW4gdGhlICpPcGVuRkRBKiBhbmQgKk1EUlAqIGRhdGEsIHJlc3BlY3RpdmVseToNCg0KYGBge3IgTkRDX0ZPUk1BVFMsIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQpsaXN0KA0KICBgT3BlbkZEQTogTkRDIEZvcm1hdHNgID0gb3BlbkZEQV9uZGNbLCB1bmlxdWUocHJvZHVjdF9uZGMpXSB8PiANCiAgICAgIHNvcnQoKSB8PiANCiAgICAgIHN0cmlfc3BsaXRfZml4ZWQoIi0iKSB8PiANCiAgICAgIG1hcF9jaHIoXCh4KSBzdHJpX2xlbmd0aCh4KSB8PiBwYXN0ZShjb2xsYXBzZSA9ICItIikpIHw+IA0KICAgICAgZnJlZHVjZShsaXN0KHNvcnQsIHRhYmxlLCBhcy5kYXRhLnRhYmxlLCBcKHgpIHNldG5hbWVzKHgsIGMoIk5EQy5Gb3JtYXQiLCAiTiIpKSkpIA0KICAsIGBNRFJQOiBOREMgRm9ybWF0c2AgPSAoYXBpX2RhdGEgJT4lIA0KICAgICAgICBzZXRuYW1lcyhzdHJpX3JlcGxhY2VfYWxsX2ZpeGVkKG5hbWVzKC4pIHw+IHRvbG93ZXIoKSwgIiAiLCAiXyIpKQ0KICAgICAgICApWywgcGFzdGUoc3RyaV9sZW5ndGgobGFiZWxlcl9jb2RlKSwgc3RyaV9sZW5ndGgocHJvZHVjdF9jb2RlKSwgc2VwID0gIi0iKV0gfD4NCiAgICAgICAgZnJlZHVjZShsaXN0KHNvcnQsIHRhYmxlLCBhcy5kYXRhLnRhYmxlLCBcKHgpIHNldG5hbWVzKHgsIGMoIk5EQy5Gb3JtYXQiLCAiTiIpKSB8PiBzZXRrZXkoTikpKSB8Pg0KICAgICAgICBkZWZpbmUoDQogICAgICAgICAgTkRDLkZvcm1hdCA9IGlmZWxzZShyYXRpbyhOLCB0eXBlID0gInBhcmV0byIsIGRlY2ltYWxzID0gNikgPCAwLjEsICJPdGhlcjxicj48c3VwPiVzIGZvcm1hdHM8L3N1cD4iLCBOREMuRm9ybWF0KSAlPiUNCiAgICAgICAgICAgIG1vZGlmeV9hdCgNCiAgICAgICAgICAgICAgLmF0ID0gd2hpY2goZ3JlcGwoIk90aGVyIiwgLikpDQogICAgICAgICAgICAgICwgLmYgPSBcKHgpIHNwcmludGYoeCwgc3VtKGdyZXBsKCJPdGhlciIsIC4pKSkNCiAgICAgICAgICAgICAgKQ0KICAgICAgICAgICwgTiA9IHN1bShOKSB+IE5EQy5Gb3JtYXQNCiAgICAgICAgICApDQogICkgfD4gDQppbWFwKFwoeCwgeSl7DQogIHBsb3RfbHkoICANCiAgICBkYXRhID0geA0KICAgICwgdHlwZSA9ICJwaWUiDQogICAgLCBsYWJlbHMgPSB+TkRDLkZvcm1hdA0KICAgICwgdmFsdWVzID0gfk4NCiAgICAsIGhvbGUgPSAwLjYNCiAgICAsIHdpZHRoID0gNTAwDQogICAgLCBoZWlnaHQgPSA0NTANCiAgICAsIHJvdGF0aW9uID0gaWZlbHNlKGdyZXBsKCJNRFJQIiwgeSksIDMzLCAwKQ0KICAgICwgbmFtZSA9IE5VTEwNCiAgICAsIHRleHRpbmZvPSdsYWJlbCtwZXJjZW50Jw0KICAgICwgaW5zaWRldGV4dG9yaWVudGF0aW9uPSdyYWRpYWwnDQogICAgKSB8Pg0KICAgIGFkZF90ZXh0KHggPSAwLjUsIHkgPSAwLjUNCiAgICAgICAgICAgICAsIHhyZWYgPSAicGFwZXIiLCB5cmVmID0gInBhcGVyIg0KICAgICAgICAgICAgICwgdGV4dCA9IGlmZWxzZShncmVwbCgiTURSUCIsIHkpLCAiTURSUCIsICJPcGVuRkRBIikNCiAgICAgICAgICAgICAsIGZvbnQgPSBsaXN0KGZhbWlseSA9ICJHZW9yZ2lhIiwgc2l6ZSA9IDIyKSkgfD4NCiAgICBwbG90bHk6OmxheW91dCgNCiAgICAgIHhheGlzID0gbGlzdChzaG93Z3JpZCA9IEZBTFNFLCB6ZXJvbGluZSA9IEZBTFNFLCBzaG93dGlja2xhYmVscyA9IEZBTFNFKQ0KICAgICAgLCB5YXhpcyA9IGxpc3Qoc2hvd2dyaWQgPSBGQUxTRSwgemVyb2xpbmUgPSBGQUxTRSwgc2hvd3RpY2tsYWJlbHMgPSBGQUxTRSkNCiAgICAgICkgfD4NCiAgICBwbG90bHk6OmNvbmZpZyhkaXNwbGF5TW9kZUJhcj0gRkFMU0UpIHw+DQogICAgdGFncyR0ZCgpDQp9KSB8Pg0KdGFncyR0cigpIHw+DQp0YWdzJHRhYmxlKCkNCmBgYA0KDQpUaGUgTURSUCBoYXMgbWFueSBtb3JlIE5EQyBzZXF1ZW5jZXMgZHVlIHRvIHRydW5jYXRpb24gb2YgbGVhZGluZyB6ZXJvZXMuIEZvcnR1bmF0ZWx5LCBhbiBOREMgc2VxdWVuY2UgaXMgYSBjb2xsZWN0aW9uIG9mIGNvZGUgc2VnbWVudHMgKHByZXNlbnQgaW4gdGhlIGRhdGEpIGNvbmNhdGVuYXRlZCB3aXRoIGEgaHlwaGVuLiBLbm93aW5nIHRoaXMsIEEgZnVuY3Rpb24gKGBjaGVja19uZGNfZm9ybWF0KClgICZtZGFzaDsgc2VlIGByIGh0bWx0b29sczo6dGFncyRhKGhyZWYgPSAiLlxcc2V0dXAuUiIsIHRhcmdldD0iX2JsYW5rIiwgInNldHVwLlIiKWAgd2FzIGNyZWF0ZWQgaW4gb3JkZXIgdG8gZGVyaXZlIGNvbmZvcm1lZCBOREMgc2VnbWVudCBzZXF1ZW5jZXMgKHVzaW5nIGxhYmVsZXIgYW5kIHByb2R1Y3QgY29kZXMpIGJhc2VkIG9uIHRoZSBPcGVuRkRBIHNlcXVlbmNlcywgYWxsb3dpbmcgdGhlIE1EUlAgYW5kIE9wZW5GREEgZGF0YSB0byBiZSBqb2luZWQgbGF0ZXIgaW4gdGhlIHByb2Nlc3MuDQoNCg0KIyMgTWFzdGVyIERydWcgRGF0YSB7LnRhYnNldCAudGFiZXQtZmFkZX0gDQoNClRoZSBPcGVuRkRBIGFuZCBNRFJQIGRhdGEgd2VyZSBqb2luZWQgdXNpbmcgdGhlIGNvbmZvcm1lZCBOREMgc2VxdWVuY2UgaW4gdGhlIHByZXZpb3VzIHN1YnNlY3Rpb24gdG8gY3JlYXRlIGBtYXN0ZXJfZHJ1Z19kYXRhYCAoKipOb3RlKio6IGR1ZSB0byB0aGUgc2l6ZSBvZiB0aGUgZGF0YSwgdGhlIGpvaW4gb3BlcmF0aW9uIHRha2VzIHNvbWUgdGltZSB3aGljaCBpcyB3aHkgdGhlIHJlc3VsdCBpcyBkaXNrLWNhY2hlZCBmb3IgbGF0ZXIgcmV0cmlldmFsLiBUaGlzIGNhY2hpbmcgYXBwcm9hY2ggaXMgdXNlZCBmb3IgZGF0YSByZXRyaWV2ZWQgb3IgY3JlYXRlZCBkdXJpbmcgdGhlIGRhdGEgd3JhbmdsaW5nIHBoYXNlKToNCg0KYGBge3IgTUFTVEVSX0RSVUdfREFUQX0NCmlmIChwYXJhbXMkcmVmcmVzaCB8fCAhIm1hc3Rlcl9kcnVnX2RhdGEiICVpbiUgLmNhY2hlJGtleXMoKSl7DQogIC5jYWNoZSRzZXQoIm1hc3Rlcl9kcnVnX2RhdGEiLCAoXCh4LCBpKXsNCiAgICB4W2kNCiAgICAgICwgb24gPSAiYWx0X25kYz09cHJvZHVjdF9uZGMiDQogICAgICAsIGFsbG93LmNhcnRlc2lhbiA9IFRSVUUNCiAgICAgICwgYDo9YChwaGFybV9jbGFzcyA9IHBoYXJtX2NsYXNzDQogICAgICAgICAgICAgLCBkZWFfc2NoZWR1bGUgPSBkZWFfc2NoZWR1bGUNCiAgICAgICAgICAgICAsIHByb2R1Y3RfdHlwZSA9IHByb2R1Y3RfdHlwZQ0KICAgICAgICAgICAgICwgcm91dGUgPSByb3V0ZQ0KICAgICAgICAgICAgICwgbWFya2V0aW5nX2NhdGVnb3J5ID0gbWFya2V0aW5nX2NhdGVnb3J5DQogICAgICAgICAgICAgKQ0KICAgICAgLCBieSA9IC5FQUNISQ0KICAgICAgXVsNCiAgICAgICwgYDo9YCgNCiAgICAgICAgcGhhcm1fY2xhc3MgPSBtYXBfY2hyKHBoYXJtX2NsYXNzLCBcKHgpIHVubGlzdCh4KSAlfHwlICJ+IikNCiAgICAgICAgLCByb3V0ZSA9IG1hcF9jaHIocm91dGUsIFwoeCkgdW5saXN0KHgpICV8fCUgIn4iKQ0KICAgICAgICApDQogICAgICBdDQogICAgfSkoDQogICAgYXBpX2RhdGEgJT4lIA0KICAgICAgc2V0bmFtZXMoc3RyaV9yZXBsYWNlX2FsbF9maXhlZChuYW1lcyguKSB8PiB0b2xvd2VyKCksICIgIiwgIl8iKSkgJT4lIA0KICAgICAgLlssIGFsdF9uZGMgOj0gbWFwMl9jaHIobGFiZWxlcl9jb2RlLCBwcm9kdWN0X2NvZGUsIGNoZWNrX25kY19mb3JtYXQpXQ0KICAgICwgb3BlbkZEQV9uZGMNCiAgICApKQ0KfQ0KaWYgKCEibWFzdGVyX2RydWdfZGF0YSIgJWluJSBscygpKXsgDQogIG1ha2VBY3RpdmVCaW5kaW5nKCJtYXN0ZXJfZHJ1Z19kYXRhIiwgZnVuY3Rpb24oKXsgLmNhY2hlJGdldCgibWFzdGVyX2RydWdfZGF0YSIpIH0sIGVudiA9IGdsb2JhbGVudigpKTsNCn0NCmBgYA0KDQojIyMgRXZlbnQgTWV0cmljcyANCg0KYG1hc3Rlcl9kcnVnX2RhdGFgIGlzIGEgZ3JlYXQgZGF0YSBzZXQgZm9yIGNvbnN0cnVjdGluZyBzaW1wbGUsIHRpbWUtYmFzZWQgbWV0cmljcy4gR2l2ZW4gdGhlIG5hdHVyYWwgb3JkZXIgb2YgdGhlIHR5cGVzIG9mIGV2ZW50cywgaXQgaXMgZWFzeSB0byBzZXR1cCBldmVudCBzZXF1ZW5jZSBtZXRyaWNzIHVzaW5nIHBhY2thZ2UgW2BsdWJyaWRhdGVgXShodHRwczovL3Jkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9sdWJyaWRhdGUvdmVyc2lvbnMvMS45LjIpLiBUaGUgbWV0cmljcyB0byBiZSBjcmVhdGVkIGFyZSBkZXNjcmliZWQgYmVsb3c6DQoNCmBgYHtyIE5EQ19FVkVOVFNfTUVUUklDU19NRVRBLCBlY2hvPUZBTFNFfQ0KLm5kY19ldmVudHNfbWV0YSA8LSBybGFuZzo6c2V0X25hbWVzKA0KICAgIGMoIkRheXMgYmV0d2VlbiBhcHByb3ZhbCBcbmFuZCBtYXJrZXQgcmVsZWFzZSINCiAgICAgICAgLCAiRGF5cyBhY3RpdmUgb24gbWFya2V0Ig0KICAgICAgICAsICJEYXlzIG1vc3QtcmVjZW50bHkgXG5hYnNlbnQgZnJvbSBtYXJrZXQiKQ0KICAgICwgYygiZGF5c190b19tYXJrZXQiLCAib25fbWFya2V0X2FnZSIsICJkYXlzX21hcmtldF9hYnNlbnQiKQ0KICAgICk7DQoNCmFwcGVuZChsaXN0KGBNZXRyaWMgTmFtZWAgPSAiRGVzY3JpcHRpb24iKSwgLm5kY19ldmVudHNfbWV0YSkgfD4gDQogIGltYXAoXCh4LCB5KSANCiAgICBpZiAoZ3JlcGwoIk1ldHJpYyIsIHkpKXsgDQogICAgICB0YWdzJHRyKA0KICAgICAgICB0YWdzJHRoKHN0eWxlID0gImJhY2tncm91bmQtY29sb3I6ICNFRUVFRUU7IHBhZGRpbmc6MnB4OyAiLCB5KQ0KICAgICAgICAsIHRhZ3MkdGgoc3R5bGUgPSAiYmFja2dyb3VuZC1jb2xvcjogI0VFRUVFRTsgcGFkZGluZy1sZWZ0OiA1cHg7ICIsIHgpDQogICAgICAgICkNCiAgICAgIH0gZWxzZSB7IA0KICAgICAgdGFncyR0cigNCiAgICAgICAgdGFncyR0ZChzdHlsZSA9ICJmb250LXdlaWdodDpib2xkOyBwYWRkaW5nOjJweDsgdGV4dC1hbGlnbjpyaWdodDsgIiwgeSkNCiAgICAgICAgLCB0YWdzJHRkKHN0eWxlID0gInBhZGRpbmctbGVmdDogNXB4OyAiLCB4KQ0KICAgICAgICApDQogICAgICB9DQogICAgKSB8PiANCiAgdGFncyR0YWJsZSgpIHw+DQogIHRhZ3MkcCgpDQpgYGANCg0KIyMjIERydWcgRXZlbnRzPGJyPkNyZWF0aW9uIA0KDQpgYGB7ciBEUlVHX0VWRU5UU19QUkVQLCBlY2hvPUZBTFNFfQ0KaWYgKHBhcmFtcyRyZWZyZXNoIHx8ICEibmRjX2V2ZW50cyIgJWluJSAuY2FjaGUka2V5cygpKXsgDQogIC50bXBfb2JqIDwtIGRlZmluZSgNCiAgICAgICAgbWFzdGVyX2RydWdfZGF0YQ0KICAgICAgICAsIH5hbHRfbmRjICsgZmRhX2FwcGxpY2F0aW9uX251bWJlciArIGZkYV9hcHByb3ZhbF9kYXRlICsgDQogICAgICAgICAgICBtYXJrZXRfZGF0ZSArIHRlcm1pbmF0aW9uX2RhdGUgKyByZWFjdGl2YXRpb25fZGF0ZQ0KICAgICAgICAsIHVuaXF1ZSguU0QpDQogICAgICAgICk7DQogIA0KICBpd2FsaygubmRjX2V2ZW50c19tZXRhLCBcKHgsIHkpeyANCiAgICAudG1wX29iaiA8PC0gbW9kaWZ5X2F0KC50bXBfb2JqLCB5LCBcKGkpeyBzZXRhdHRyKGksICJsYWJlbCIsIHgpIH0pIA0KICB9KQ0KDQogIC5jYWNoZSRzZXQoIm5kY19ldmVudHMiLCAudG1wX29iaikNCn0NCg0KaWYgKCEibmRjX2V2ZW50cyIgJWluJSBscygpKXsgDQogIG1ha2VBY3RpdmVCaW5kaW5nKCJuZGNfZXZlbnRzIiwgZnVuY3Rpb24oKXsgLmNhY2hlJGdldCgibmRjX2V2ZW50cyIpIH0sIGVudiA9IGdsb2JhbGVudigpKSANCn0NCmBgYA0KDQpNeSBuZXh0IHRhc2sgd2FzIHRvIGFkZCB0aGUgZGF0ZS1kaWZmZXJlbnRpYWwgbWV0cmljcyBtZW50aW9uZWQgaW4gdGhlIHByZXZpb3VzIHN1YnNlY3Rpb24uICBBcyBhbiBpbnRlcm1lZGlhdGUgb2JqZWN0LCBJIGNyZWF0ZWQgYG5kY19ldmVudHNgIGJ5IGxvb2tpbmcgYXQgd2hhdCBhcHBlYXJzIHRvIHRoZSBiZSBuYXR1cmFsIGNocm9ub2xvZ3kgb2YgZGF0ZXM6IGBmZGFfYXBwcm92YWxfZGF0ZWAgLT4gYG1hcmtldF9kYXRlYCAtPiBgdGVybWluYXRpb25fZGF0ZWAgLT4gYHJlYWN0aXZhdGlvbl9kYXRlYC4gIA0KDQpTb21lIG9mIHRoZSB2YWx1ZXMgaW4gY29sdW1ucyBgdGVybWluYXRpb25fZGF0ZWAgYW5kIGByZWFjdGl2YXRpb25fZGF0ZWAgYXJlIGBOQWAgaW5kaWNhdGluZyB0aGUgZXZlbnQgZGlkIG5vdCBoYXBwZW4uIFRoaXMgd291bGQgb2J2aW91c2x5IG5lZWQgdG8gYmUgYWRkcmVzc2VkIGluIGRlcml2aW5nIHRoZSBtZXRyaWNzIGxvZ2ljLCBhbmQgYWZ0ZXIgc2V2ZXJhbCByb3VuZHMgb2YgdHJpYWwtYW5kLWVycm9yLCBJIHdvcmtlZCBvdXQgc3VjaCBsb2dpYywgZGlzY292ZXJpbmcgdGhlIGZvbGxvd2luZyBpbiB0aGUgcHJvY2VzczoNCg0KLSBUaGUgbWV0cmljcyBhcmUgaGllcmFyY2hpY2FsbHktY29udGluZ2VudCBiYXNlZCBvbiB3aGV0aGVyIG9yIG5vdCBgTkFgIHZhbHVlcyBleGlzdCBhbmQgaWYgc28sIHdoaWNoIG9mIGB0ZXJtaW5hdGlvbl9kYXRlYCwgYHJlYWN0aXZhdGlvbl9kYXRlYCwgb3IgYm90aA0KLSBTb21lIHZhbHVlcyBpbiBgdGVybWluYXRpb25fZGF0ZWAgYW5kIGByZWFjdGl2YXRpb25fZGF0ZWAgYXJlIGZ1dHVyZS1kYXRlZCByZWxhdGl2ZSB0byAidG9kYXkiOiB0aGVzZSB3ZXJlIGNvbnZlcnRlZCB0byBgTkFgIGJlZm9yZSBkZXJpdmluZyB0aGUgbWV0cmljcyBhcyB0aGV5IGhhdmVuJ3QgaGFwcGVuZWQgeWV0IChgTkFgICRcZXF1aXYkIERpZG4ndCBoYXBwZW4gKF95ZXRfKSkNCi0gQSBzbWFsbCBzdWJzZXQgb2Ygb2JzZXJ2YXRpb25zIGhhdmluZyB0aGUgRkRBIGFwcHJvdmFsIGRhdGUgKiphZnRlcioqIHRoZSBsaXN0ZWQgbWFya2V0IGRhdGUNCg0KVGhlIHJlc3VsdGluZyBvYmplY3Qgd2FzIGNhcHR1cmVkIGluIGBuZGNfZXZlbnRzX2NsZWFuYDoNCg0KYGBge3IgRFJVR19FVkVOVFNfTUFLRX0NCm5kY19ldmVudHNfY2xlYW4gPC0geyANCiAgZGVmaW5lKA0KICAgIG5kY19ldmVudHMNCiAgICAsIG1vZGlmeV9hdCguU0QsIGMoInRlcm1pbmF0aW9uX2RhdGUiLCAicmVhY3RpdmF0aW9uX2RhdGUiKSwgXCh4KSBpZmVsc2UodG9kYXkoKSA8IHgsIE5BLCB4KSkNCiAgICAsIGNiaW5kKA0KICAgICAgICAuU0QNCiAgICAgICAgLCBkZWZpbmUoew0KICAgICAgICAgICAgLlNEWywgZmRhX2FwcHJvdmFsX2RhdGU6cmVhY3RpdmF0aW9uX2RhdGVdWywgbWFwKC5TRCwgYXMubnVtZXJpYyldIHw+IA0KICAgICAgICAgICAgICAjIGRwbHlyOjpzbGljZV9zYW1wbGUocHJvcCA9IDAuNCkgfD4NCiAgICAgICAgICAgICAgYXBwbHkoMSwgXCh4KXsNCiAgICAgICAgICAgICAgICBjKHgsIGRpZmYoeCkgfD4gbW9kaWZ5X2lmKGlzLm5hLCBcKGkpIDApIHw+IHNpZ24oKSAlPiUgLlstMV0pIHw+IA0KICAgICAgICAgICAgICAgICAgYXMubGlzdCgpIHw+DQogICAgICAgICAgICAgICAgICBtb2RpZnlfYXQoYyg1LCA2KSwgXChpKSBpID09IDEpICU+JSANCiAgICAgICAgICAgICAgICAgIHJsYW5nOjpzZXRfbmFtZXMobmFtZXMoLilbYygxOjQpXSwgcGFzdGUwKG5hbWVzKC4pW2MoNSwgNildLCAiLmJvb2wiKSkNCiAgICAgICAgICAgICAgICB9LCBzaW1wbGlmeSA9IEZBTFNFKSB8Pg0KICAgICAgICAgICAgICByYmluZGxpc3QoKQ0KICAgICAgICAgICAgfQ0KICAgICAgICAgICwgZGF5c190b19tYXJrZXQgPSBtYXJrZXRfZGF0ZSAtIGZkYV9hcHByb3ZhbF9kYXRlDQogICAgICAgICAgLCBvbl9tYXJrZXRfYWdlID0gDQogICAgICAgICAgICAgIGFwcGx5KC5TRFssIC4odGVybWluYXRpb25fZGF0ZS5ib29sLCByZWFjdGl2YXRpb25fZGF0ZS5ib29sLCB0ZXJtaW5hdGlvbl9kYXRlKV0NCiAgICAgICAgICAgICAgICAgICAgLCAxLCBmdW5jdGlvbihpKXsgaWZlbHNlKGlbWzFdXSwgaWZlbHNlKGlbWzJdXSwgdG9kYXkoKSwgaVtbM11dKSwgdG9kYXkoKSkgfSkgLQ0KICAgICAgICAgICAgICBhcHBseSguU0RbLCAuKHRlcm1pbmF0aW9uX2RhdGUuYm9vbCwgcmVhY3RpdmF0aW9uX2RhdGUuYm9vbCwgbWFya2V0X2RhdGUsIHJlYWN0aXZhdGlvbl9kYXRlKV0NCiAgICAgICAgICAgICAgICAgICAgLCAxLCBmdW5jdGlvbihpKXsgaWZlbHNlKGlbWzFdXSwgaWZlbHNlKGlbWzJdXSwgaVtbNF1dLCBpW1szXV0pLCBpW1szXV0pIH0pDQogICAgICAgICAgLCBkYXlzX21hcmtldF9hYnNlbnQgPSANCiAgICAgICAgICAgICAgYXBwbHkoLlNEWywgLihyZWFjdGl2YXRpb25fZGF0ZS5ib29sLCByZWFjdGl2YXRpb25fZGF0ZSldDQogICAgICAgICAgICAgICAgICAgICwgMSwgZnVuY3Rpb24oaSl7IGlmZWxzZShpW1sxXV0sIGlbWzJdXSwgdG9kYXkoKSkgfSkgLQ0KICAgICAgICAgICAgICAgIGFwcGx5KC5TRFssIC4odGVybWluYXRpb25fZGF0ZS5ib29sLCB0ZXJtaW5hdGlvbl9kYXRlKV0NCiAgICAgICAgICAgICAgICAgICAgLCAxLCBmdW5jdGlvbihpKXsgaWZlbHNlKGlbWzFdXSwgaVtbMl1dLCB0b2RheSgpKSB9KQ0KICAgICAgICAgICwgfmRheXNfdG9fbWFya2V0ICsgb25fbWFya2V0X2FnZSArIGRheXNfbWFya2V0X2Fic2VudA0KICAgICAgICAgICkNCiAgICAgICkNCiAgICAsIG1vZGlmeV9hdCguU0QsIGMoInRlcm1pbmF0aW9uX2RhdGUiLCAicmVhY3RpdmF0aW9uX2RhdGUiKSwgXCh4KSBhcy5EYXRlKHgsIG9yaWdpbiA9ICIxOTcwLTAxLTAxIikpDQogICl9DQoNCiMNCihcKHgsIGksIGJ5KXsNCiAgaSA8LSBkZWZpbmUoeFtpLCBvbiA9IGJ5LCBhbGxvdy5jYXJ0ZXNpYW4gPSBUUlVFXSkgOw0KICBpbWFwKC5uZGNfZXZlbnRzX21ldGEsIFwoeCwgeSl7DQogICAgcmxhbmc6OmluamVjdChkZXNjcih4ID0gbW9kaWZ5X2F0KGksIHksIFwoaikgYXMubnVtZXJpYyhqLCB1bml0cyA9ICJkYXlzIikpLCB2YXIgPSAhIXJsYW5nOjpzeW0oeSksIHRyYW5zcG9zZSA9ICFUUlVFKSkgfD4gDQogICAgICB2aWV3KG1ldGhvZCA9ICJyZW5kZXIiLCB0YWJsZS5jbGFzc2VzID0gJ211bHRpX3N0YXQnLCBjdXN0b20uY3NzID0gIm1hcmtkb3duLmNzcyIpIHw+DQogICAgICB0YWdzJHRkKCkNCiAgfSkNCn0pKG1hc3Rlcl9kcnVnX2RhdGEsIG5kY19ldmVudHNfY2xlYW4sIGMoImFsdF9uZGMiLCAiZmRhX2FwcGxpY2F0aW9uX251bWJlciIsICJtYXJrZXRfZGF0ZSIsICJ0ZXJtaW5hdGlvbl9kYXRlIiwgInJlYWN0aXZhdGlvbl9kYXRlIiwgImZkYV9hcHByb3ZhbF9kYXRlIikpIHw+DQp0YWdzJHRyKCkgfD4NCnRhZ3MkdGFibGUoKQ0KYGBgDQoNClNvbWUgb2YgdGhlICdNYXgnLydNaW4nIHZhbHVlcyBhcmUgbmVnYXRpdmU7IGhvd2V2ZXIsIHRoZSBudW1iZXIgb2YgcmVjb3JkcyBpcyByZWxhdGl2ZWx5IHNtYWxsIGFuZCwgbW9yZSBpbXBvcnRhbnRseSwgZXhwbGFpbmFibGU6DQoNCi0gYGRheXNfdG9fbWFya2V0YDogQXBwcm92YWwgb2NjdXJyZWQgYWZ0ZXIgdGhlIG1hcmtldCBkYXRlDQotIGBkYXlzX21hcmtldF9hYnNlbnRgOiBSZWNvcmRzIHdoZXJlIHRoZSB0ZXJtaW5hdGlvbiBkYXRlIHdhcyBub24tYE5BYCBidXQgYWZ0ZXIgdGhlIG1hcmtldCBkYXRlDQoNCiMjIyBEcnVnIEV2ZW50czxicj5WaXN1YWxpemF0aW9uIA0KDQpDb21iaW5pbmcgdGhlIG1hc3RlciBkcnVnIGRhdGEgYW5kIGV2ZW50IGRhdGEgKGBtYXN0ZXJfZHJ1Z19kYXRhYCArIGBuZGNfZXZlbnRzX2NsZWFuYCksIGFmdGVyIHNvbWUgdHJpYWwtYW5kLWVycm9yLCBJIHNldHRsZWQgb24gdGhlIGZvbGxvd2luZyBzaG93aW5nIHRoZSByb290LW1lYW4tc3F1YXJlIG9mIG1ldHJpYyB2YWx1ZXMgZ3JvdXBlZCBieSByb3V0ZSBvZiBhZG1pbmlzdHJhdGlvbjoNCg0KYGBge3IgRFJVR19FVkVOVFNfVklaLCBlY2hvPUZBTFNFfSANCi5jYWNoZSRzZXQoImRydWdfb2JzX2RhdGEiLCB7DQogIG5kY19ldmVudHNfY2xlYW5bDQogICAgbWFzdGVyX2RydWdfZGF0YQ0KICAgICwgb24gPSBjKCJhbHRfbmRjIiwgImZkYV9hcHBsaWNhdGlvbl9udW1iZXIiLCAiZmRhX2FwcHJvdmFsX2RhdGUiLCAibWFya2V0X2RhdGUiLCAidGVybWluYXRpb25fZGF0ZSIsICJyZWFjdGl2YXRpb25fZGF0ZSIpDQogICAgLCBhbGxvdy5jYXJ0ZXNpYW4gPSBUUlVFDQogICAgLCBub21hdGNoID0gMA0KICAgIF1bLCByb3V0ZV9zaXplIDo9IHVuaXF1ZU4obmRjKSwgYnkgPSByb3V0ZV0gfD4gDQogICAgdW5pcXVlKCkgfD4NCiAgICBtb2RpZnlfYXQoYygiZGF5c190b19tYXJrZXQiLCAib25fbWFya2V0X2FnZSIsICJkYXlzX21hcmtldF9hYnNlbnQiKSwgXCh4KSBhcy5kaWZmdGltZSh4LCB1bml0cyA9ICJkYXlzIikpDQogIH0pDQoNCmlmICghImRydWdfb2JzX2RhdGEiICVpbiUgbHMoKSl7IA0KICBtYWtlQWN0aXZlQmluZGluZygNCiAgICAiZHJ1Z19vYnNfZGF0YSINCiAgICAsIGZ1bmN0aW9uKCkgLmNhY2hlJGdldCgiZHJ1Z19vYnNfZGF0YSIpDQogICAgLCBlbnYgPSBnbG9iYWxlbnYoKQ0KICAgICkNCn0NCg0KLnBsb3QgPC0gcGxvdF9seSh3aWR0aCA9IDk2MCwgaGVpZ2h0ID0gNzAwKQ0KDQpzdXBwcmVzc1dhcm5pbmdzKGRydWdfb2JzX2RhdGFbLCAuKHJvdXRlLCBkYXlzX3RvX21hcmtldCwgb25fbWFya2V0X2FnZSwgZGF5c19tYXJrZXRfYWJzZW50KV0gfD4gDQogICMgZHBseXI6OnNsaWNlX3NhbXBsZShwcm9wID0gMC4xKSB8Pg0KICBtZWx0KGlkLnZhcnMgPSAicm91dGUiLCB2YXJpYWJsZS5uYW1lID0gIk1ldHJpYyIpIHw+IA0KICBzZXRvcmRlcihyb3V0ZSwgTWV0cmljLCB2YWx1ZSkgfD4NCiAgc3BsaXQoYnkgPSAicm91dGUiKSB8Pg0KICBpd2FsayhcKHgsIHkpeyANCiAgICB4IDwtIGRlZmluZSh4LCBtYXAoLlNELCBcKGkpIGNhbGMucm1zKGFzLm51bWVyaWMoaSkgfD4gbW9kaWZ5X2lmKGlzLm5hLCBcKGkpIDApKSB8PiBhcy5kaWZmdGltZSh1bml0cyA9ICJkYXlzIikpIH5NZXRyaWMpOw0KICAgIC5wbG90IDw8LSBhZGRfdHJhY2UoDQogICAgICAgIHAgPSAucGxvdA0KICAgICAgICAsIHR5cGUgPSAnc2NhdHRlcnBvbGFyJw0KICAgICAgICAsIGZpbGwgPSAndG9zZWxmJw0KICAgICAgICAsIHIgPSBpZmVsc2UoeCR2YWx1ZSA9PSAwLCAwLCBsb2coYXMubnVtZXJpYyh4JHZhbHVlKSwgYmFzZSA9IDMwKSkNCiAgICAgICAgLCB0aGV0YSA9IHgkTWV0cmljDQogICAgICAgICwgaG92ZXJpbmZvID0gInRleHQiDQogICAgICAgICwgaG92ZXJ0ZXh0ID0gc3ByaW50ZigNCiAgICAgICAgICAgICAgIjxiPiVzPC9iPjxicj4lLjJmICVzIg0KICAgICAgICAgICAgICAsIHkNCiAgICAgICAgICAgICAgLCBpZmVsc2UoeCR2YWx1ZSA+IDM2NSwgeCR2YWx1ZS8zNjUsIHgkdmFsdWUpDQogICAgICAgICAgICAgICwgaWZlbHNlKHgkdmFsdWUgPiAzNjUsICJZZWFycyIsICJEYXlzIikNCiAgICAgICAgICAgICAgKQ0KICAgICAgICAsIG5hbWUgPSB5DQogICAgICAgICwgbW9kZSA9ICJtYXJrZXJzIg0KICAgICAgICApIA0KICB9KSkNCg0KLnBsb3QgfD4NCiAgY29uZmlnKG1hdGhqYXggPSAiY2RuIikgfD4NCiAgbGF5b3V0KA0KICAgIHRpdGxlID0gbGlzdCgNCiAgICAgICAgdGV4dCA9IEhUTUwoIk1ldHJpY3MgUmFkYXIgYnkgUm91dGUgb2YgQWRtaW5pc3RyYXRpb24gKExvZzxzdWI+MzA8L3N1Yj4gRGF5cyk8YnI+PHNwYW4gc3R5bGU9J2ZvbnQtc2l6ZTpzbWFsbGVyOyAnPihEb3VibGUtIG9yIHNpbmdsZS1jbGljayBsZWdlbmQgaXRlbXMpIikNCiAgICAgICAgLCBsZWdlbmQgPSBsaXN0KGZvbnQgPSBsaXN0KHNpemUgPSAxMCkpDQogICAgICAgICwgZm9udCA9IGxpc3QoZmFtaWx5ID0gIkdlb3JnaWEiKQ0KICAgICAgICApDQogICAgLCBtYXJnaW4gPSBsaXN0KHQgPSAtMC41KSANCiAgICApIHw+DQogIHRhZ3MkcCgpDQpgYGANCg0K